
#include "processmanager.h"
#include "process.h"
#include "commonfunctions.h"

#include <unistd.h>
#include <sys/wait.h>
#include <iostream>
#include <sstream>


const ProcessManager& ProcessManager::instance() {

	static ProcessManager instance = ProcessManager();

	return instance;

}


void ProcessManager::onSigchld(int status) {

	std::stringstream msg;

	//msg << "job " << pid << + ": ";

	// TODO remove from stack

	if (WIFEXITED(status)) {
		int ret = WEXITSTATUS(status);
		msg << (0 == ret ? "done" : ("failed with code " + ret));
	} else if (WIFSIGNALED(status)) {
		msg << "terminated by signal " << WTERMSIG(status);
	}

	msg << ".";
	std::cout << msg.str() << std::endl;

	// TODO stop and resume

}


ProcessManager::ProcessManager()
		: m_prcs() {

	//signal(SIGCHLD, ProcessManager::onSigchld);
	// TODO error handling

}


void ProcessManager::setSignals(bool handle) {

	void (*handler)(int) = handle ? SIG_DFL : SIG_IGN;

	signal(SIGINT, handler);
	signal(SIGQUIT, handler);
	signal(SIGTSTP, handler);
	signal(SIGTTIN, handler);
	signal(SIGTTOU, handler);
	signal(SIGCHLD, handler);

}


// returns NULL in the child process
const Process* ProcessManager::spawn(bool isBg, int gid) {

	int pid = fork();

	CommonFunctions::exitOnError(pid < 0);

	if (0 == gid)
		gid = getpid();

	if (0 == pid) { // child
		CommonFunctions::exitOnError(setpgid(getpid(), gid) < 0);
		setSignals(true);
		return NULL;
	}

	CommonFunctions::exitOnError(setpgid(pid, gid) < 0);

	if (!isBg)
		waitpid(pid, NULL, 0);

	const Process* p = new Process(pid);
	m_prcs.push_back(p);
	return p;

}


void ProcessManager::exec(Command cmd) {

	const Process* p;
	int gid = 0;

	switch (cmd.size()) {
		case 0:
			return;
		case 1: // no piping
			p = spawn(cmd.isBg(), gid);
			if (NULL == p)
				Process::run(cmd[0]);
			delete p;
			return;
	}

	// TODO error handling for invalid file descriptors!
	// TODO shorten code

	// make first pipe and process
	int fd[2];
	pipe(fd);
	p = this->spawn(true, gid);

	// child process
	if (NULL == p) {
		close(fd[0]);
		dup2(fd[1], STDOUT_FILENO);
		Process::run(cmd[0]); // no return
	}

	gid = p->getId();

	// chain inner processes
	int i = 1;
	for (; i < cmd.size() - 1; i++) {

		close(fd[1]);
		int tmpFd[2];
		pipe(tmpFd);
		fd[1] = tmpFd[1];
		p = spawn(true, gid);

		// child process
		if (NULL == p) {
			close(tmpFd[0]);
			dup2(fd[0], STDIN_FILENO);
			dup2(fd[1], STDOUT_FILENO);
			Process::run(cmd[i]); // no return
		}

		close(fd[0]);
		fd[0] = tmpFd[0];

	}

	close(fd[1]);
	p = spawn(cmd.isBg(), gid); // last process

	// child process
	if (NULL == p) {
		dup2(fd[0], STDIN_FILENO);
		Process::run(cmd[i]); // no return
	}
	close(fd[0]);

	// wait
	if (!cmd.isBg())
		p->waitFor();

}


ProcessManager::~ProcessManager() {

	// TODO terminate all processes
	for (unsigned int i = 0; i < m_prcs.size(); i++) {
		delete m_prcs[i];
	}

}
